using System;
using System.IO;
using System.Diagnostics;
using System.Reflection;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Weazel.Gezel.Simulator.Reference.Test
{
  /// <summary>
  /// Summary description for TestSimulator
  /// </summary>
  [TestClass]
  public class TestSimulator
  {
    public static void Main()
    {
      TestSimulator s = new TestSimulator();
      s.TestAes();
    }

    [TestMethod]
    public void TestAes()
    {
      runModelTest("aes.fdl", 50);
    }

    [TestMethod]
    public void TestFulladder()
    {
      runModelTest("fulladder.fdl", 20);
    }

    [TestMethod]
    public void TestJanTest()
    {
      runModelTest("jantest.fdl", 10);
    }

    [TestMethod]
    public void TestCast()
    {
      runModelTest("cast.fdl", 1);
    }

    [TestMethod]
    public void TestChildDatapaths()
    {
      runModelTest("testchilddp.fdl", 16);
    }

    [TestMethod]
    public void TestGf8Inv()
    {
      runModelTest("gf8inv.fdl", 10);
    }

    [TestMethod]
    public void TestConcatenation()
    {
      runModelTest("testconcat.fdl", 10);
    }

    [TestMethod]
    public void TestEuclid()
    {
      runModelTest("euclid.fdl", 30);
    }

    [TestMethod]
    public void TestConditionalFsm()
    {
      runModelTest("testcondfsm.fdl", 20);
    }

    [TestMethod]
    public void TestIpChecksum()
    {
      runModelTest("ipchecksum.fdl", 40);
    }


    [TestMethod]
    public void TestRange()
    {
      runModelTest("testrange.fdl", 50);
    }

    [TestMethod]
    public void TestScripted()
    {
      runModelTest("scripted.fdl", 10);
    }

    [TestMethod]
    public void TestTernary()
    {
      runModelTest("testternary.fdl", 10);
    }

    [TestMethod]
    public void TestLookupTable()
    {
      runModelTest("testlookup.fdl", 20);
    }

    [TestMethod]
    public void TestRamblock()
    {
      runModelTest("ramblock.fdl", 350);
    }

    [TestMethod]
    public void TestRom()
    {
      runModelTest("testrom.fdl", 10);
    }

    /// <summary>
    /// Checks a gezel model by executing it in the
    /// reference simulator and comparing the results
    /// read from standard out with the golden reference
    /// file.
    /// </summary>
    /// <param name="filename">name of the gezel model file</param>
    /// <param name="cycles">number of cycles to execute</param>
    private static void runModelTest(string filename, int cycles)
    {
      // due to shadow copying in NUnit/Visual Studio Tests it is complicated
      // to locate the place where the model files are
      // store, which is relative to this test assembly.
      Assembly asm = Assembly.GetExecutingAssembly();
      string codeBase = asm.CodeBase.Replace("file:///", "").Replace("/", "\\");
      string baseDir = new FileInfo(codeBase).DirectoryName;
      filename = baseDir + "\\" + filename;

      string previousDirectory = System.IO.Directory.GetCurrentDirectory();
      System.IO.Directory.SetCurrentDirectory(baseDir);

      Assert.IsTrue(
        File.Exists(filename),
        string.Format("model file '{0}' does not exist", filename)
        );

      StreamReader stdOut = runGezelModel(baseDir, filename, cycles);
      StreamReader golden = getGoldenFile(filename);

      checkTestResult(stdOut, golden);

      // reset working dir
      System.IO.Directory.SetCurrentDirectory(previousDirectory);
    }

    /// <summary>
    /// Compares the results of a test run with a
    /// golden reference file on a line by line basis.
    /// 
    /// Empty lines in the test output are disregarded as
    /// are commented files in the golden reference file.
    /// </summary>
    /// <param name="stdOut">stream with actual test data</param>
    /// <param name="golden">steam with reference data</param>
    private static void checkTestResult(StreamReader stdOut, StreamReader golden)
    {
      string outputLine = null, goldenLine = null;
      int outputLineNo = 0;
      while ((outputLine = stdOut.ReadLine()) != null)
      {
        outputLineNo++;
        if (outputLine.Trim() == string.Empty)
        {
          continue;
        }

        // read a line from the golden file, skip lines with comments (//)
        while ((goldenLine = golden.ReadLine()) != null && goldenLine.TrimStart().StartsWith("//"))
          ;

        Assert.IsTrue(goldenLine != null, "no more lines of input in golden file");
        Assert.AreEqual(goldenLine, outputLine, string.Format("files differ on line {0}", outputLineNo));
      }
      Console.Error.WriteLine("finished checking result");
    }

    /// <summary>
    /// Runs a gezel model file i a separate process for the
    /// specified number of cycles and collects data written 
    /// to standard output.
    /// </summary>
    /// <param name="baseDir">location of the reference simulator executable</param></param>
    /// <param name="filename">name of the gezel model file</param>
    /// <param name="cycles">the number of cycles to run the simulation</param>
    /// <returns></returns>
    private static StreamReader runGezelModel(string baseDir, string filename, int cycles)
    {
      ProcessStartInfo startInfo = new ProcessStartInfo();
      startInfo.FileName = baseDir + "\\refsim.exe";
      startInfo.Arguments = string.Format("\"{0}\" {1}", filename, cycles);
      startInfo.RedirectStandardOutput = true;
      startInfo.RedirectStandardError = true;
      startInfo.UseShellExecute = false;
      startInfo.CreateNoWindow = true;

      Assert.IsTrue(
        File.Exists(startInfo.FileName),
        string.Format("simulator executable '{0}' not found", startInfo.FileName)
        );

      Process process = new Process();
      process.StartInfo = startInfo;
      process.Start();

      // the WaitForExit method causes
      // problems when running the 
      // ramblock test from the NUnit gui.
      //
      process.WaitForExit(5000);

      Console.Error.WriteLine("process exited");

      return process.StandardOutput;
    }

    /// <summary>
    /// Fetches the golden reference file (if it exist!) 
    /// and returns it a stream to it. The filename of the
    /// golden file is inferred from the name of the model file.
    /// 
    /// It is assumed that the model example.fdl has a corresponding
    /// golden reference file named example.golden.
    /// </summary>
    /// <param name="filename">name of the gezel model file</param>
    /// <returns>a stream to the golden file</returns>
    private static StreamReader getGoldenFile(string filename)
    {
      Assert.IsTrue(
        filename.EndsWith(".fdl"),
        "expected .fdl file"
        );

      string goldenFilename = string.Format("{0}.golden", filename.Remove(filename.Length - 4, 4));

      Assert.IsTrue(
        File.Exists(goldenFilename),
        string.Format("golden file '{0}' not found", goldenFilename)
        );

      return new StreamReader(goldenFilename);
    }
  }
}
